11.OpenGL ES滤镜

给图像添加滤镜本质就是图片处理,也就是对图片的像素进行计算,简单来说,图像处理的方法可以分为三类:

  • 点算:当前像素的处理只和自身的像素值有关,和其他像素无关,比如灰度处理。
  • 领域算:当前像素的处理需要和相邻的一定范围内的像素有关,比如高斯模糊。
  • 全局算:在全局上对所有像素进行统一变换,比如几何变换。

灰色滤镜

和前一篇文章基本一样,只是修改一下片段着色器的代码,原理就是在片段着色器中去处理颜色,让RGB三个通道的颜色取均值:

#version 300 es
#extension GL_OES_EGL_image_external_essl3 : require
precision mediump float;

in vec2 v_texCoord;
out vec4 outColor;
uniform samplerExternalOES s_texture;

//灰度滤镜,具体去处理颜色
void grey(inout vec4 color){
    float weightMean = color.r * 0.3 + color.g * 0.59 + color.b * 0.11;
    color.r = color.g = color.b = weightMean;
}

void main(){
    //拿到颜色值
    vec4 tmpColor = texture(s_texture, v_texCoord);
    //对颜色值进行处理
    grey(tmpColor);
    //将处理后的颜色值输出到颜色缓冲区
    outColor = tmpColor;
}

反色滤镜

RGB三个通道的颜色取反,而alpha通道不变。

灰色滤镜

让RGB三个通道的颜色取均值

位移滤镜

纹理默认传入的读取范围是(0,0)到(1,1)内的颜色值。如果对读取的位置进行调整修改,那么就可以做出各种各样的效果,例如缩放动画就是让读取的范围改成(-1,-1)到(2,2)。

看完了疯了是不是,要做个滤镜效果,各种计算我实在弄不明白

最简单的方法就是通过LUT方法,通过设计师提供的LUT文件来实现预定的滤镜效果。基本思路如下:

  • 准备LUT文件
  • 加载LUT文件到OpenGL纹理
  • 将纹理传递给片段着色器
  • 根据LUT,在片段着色器中对图像的颜色值进行映射,得到滤镜后的颜色进行输出

离屏渲染

之前已经将相机的预览数据输出到OpenGL的纹理上,渲染的时候OpenGL直接将纹理渲染到屏幕上。但是如果想要对纹理进行进一步的处理,就不能直接渲染到屏幕上,而是需要先渲染到屏幕外的缓冲区(FrameBuffer)处理完后再渲染到屏幕。渲染到缓冲区的操作就是离屏渲染。主要步骤如下:

  • 准备离屏渲染所需要的FrameBuffer和纹理对象。
  • 切换渲染目标(屏幕->缓冲区)
  • 执行渲染
  • 重置渲染目标(缓冲区 -> 屏幕)

视频播放滤镜实现

首先需要在GLSurfaceView.Renderer的实现类中提供一个setFilter()的方法,然后在onDrawFrame()的时候再去调用这个Filter的onDraw()方法,所以滤镜的编写主要是靠基类。

  • 先抽取BaseFilter类,封装好每个滤镜的着色器及onDraw等方法。
  • 不同的滤镜都继承该BaseFilter类,然后实现有区别的部分。其实每个不同滤镜的区别就是着色器的不同。
  • 在Renderder接口的实现类中去创建不同的Filter类,然后在onDrawFrame方法中去调用该filter.onDraw方法。

滤镜的不同大部分只需要修改片元着色器就可以了。

滤镜的基类

  • onCreate():创建
  • onSizeChange():滤镜尺寸改变
  • onDraw():绘制每一帧
  • onDestroy():销毁,用于资源回收。
public class BaseFilter {
    private static final int POSITION_COMPONENT_COUNT = 2;
    private static final int TEXTURE_COMPONENT_COUNT = 2;

    protected int mProgram;
    @RawRes
    private int mVertexShaderResId;
    @RawRes
    private int mFragmentShaderResId;

    private boolean mInited;

    private int vertexPosition;
    private int texturePosition;
    private int samplerTexturePosition;

    //渲染线程
    private LinkedList<Runnable> mRunOnDraw = new LinkedList();

    public BaseFilter() {
        this(R.raw.video_no_filter_vertex_shader, R.raw.video_no_filter_fragment_shader);
    }

    public BaseFilter(@RawRes int vertexShaderResId, @RawRes int fragmentShaderResId) {
        mVertexShaderResId = vertexShaderResId;
        mFragmentShaderResId = fragmentShaderResId;
    }

    public void init() {
        if (!mInited) {
            onInit();
            mInited = true;
            onInited();
        }
    }

    public void onInit() {
        handleProgram(MyApplication.getInstance(), mVertexShaderResId, mFragmentShaderResId);
        vertexPosition = glGetAttribLocation("vPosition");
        texturePosition = glGetAttribLocation("vCoordPosition");
        samplerTexturePosition = glGetUniformLocation("uSamplerTexture");
    }

    /**
     * readResource -> compileShader -> linkProgram -> useProgram
     *
     * @param context
     * @param vertexShader
     * @param fragmentShader
     */
    protected void handleProgram(@NonNull Context context, @RawRes int vertexShader, @RawRes int fragmentShader) {
        String vertexShaderStr = ResReadUtils.readResource(context, vertexShader);
        int vertexShaderId = ShaderUtils.compileVertexShader(vertexShaderStr);
        //编译片段着色程序
        String fragmentShaderStr = ResReadUtils.readResource(context, fragmentShader);
        int fragmentShaderId = ShaderUtils.compileFragmentShader(fragmentShaderStr);
        //连接程序
        mProgram = ShaderUtils.linkProgram(vertexShaderId, fragmentShaderId);
        //在OpenGLES环境中使用程序
        GLES30.glUseProgram(mProgram);
    }

    public void onInited() {

    }

    public final void destroy() {
        mInited = false;
        GLES30.glDeleteProgram(mProgram);
        onDestroy();
    }

    public void onDestroy() {

    }

    public void onDraw(final int textureId, final FloatBuffer mVertextBuffer,
                       final FloatBuffer mTextureBuffer) {
        runPendingOnDrawTasks();
        if (!mInited) {
            return;
        }

        glVertexAttribPointer(vertexPosition, POSITION_COMPONENT_COUNT, GLES30.GL_FLOAT, false, 0, mVertextBuffer);
        glVertexAttribPointer(texturePosition, TEXTURE_COMPONENT_COUNT, GLES30.GL_FLOAT, false, 0, mTextureBuffer);
        GLES30.glEnableVertexAttribArray(vertexPosition);
        GLES30.glEnableVertexAttribArray(texturePosition);
        GLES30.glUniform1i(samplerTexturePosition, 0);
//        if (textureId != GL.NO_TEXTURE) {
//            GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
//            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
//            GLES20.glUniform1i(glUniformTexture, 0);
//        }

        onDrawArraysPre();
        GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, 4);
        GLES30.glDisableVertexAttribArray(vertexPosition);
        GLES30.glDisableVertexAttribArray(texturePosition);
    }

    protected void runPendingOnDrawTasks() {
        while (!mRunOnDraw.isEmpty()) {
            mRunOnDraw.removeFirst().run();
        }
    }


    /**
     * 设置着色器中对象float值
     */
    protected void setFloat(final int location, final float floatValue) {
        runOnDraw(new Runnable() {
            @Override
            public void run() {
                GLES30.glUniform1f(location, floatValue);
            }
        });
    }

    /**
     * 设置着色器中对象组值float值
     */
    protected void setFloatVec2(final int location, final float[] arrayValue) {
        runOnDraw(new Runnable() {
            @Override
            public void run() {
                GLES30.glUniform2fv(location, 1, FloatBuffer.wrap(arrayValue));
            }
        });
    }

    /**
     * 设置着色中数组值
     */
    protected void setFloatVec3(final int location, final float[] arrayValue) {
        runOnDraw(new Runnable() {
            @Override
            public void run() {
                GLES20.glUniform3fv(location, 1, FloatBuffer.wrap(arrayValue));
            }
        });
    }

    /**
     * 设置着色器中对象组值float值
     */
    protected void setFloatVec4(final int location, final float[] arrayValue) {
        runOnDraw(new Runnable() {
            @Override
            public void run() {
                GLES20.glUniform4fv(location, 1, FloatBuffer.wrap(arrayValue));
            }
        });
    }

    /**
     * 设置着色器中3维矩阵的值
     */
    protected void setUniformMatrix3f(final int location, final float[] matrix) {
        runOnDraw(new Runnable() {
            @Override
            public void run() {
                GLES20.glUniformMatrix3fv(location, 1, false, matrix, 0);
            }
        });
    }

    /**
     * 设置着色器中4维矩阵的值
     */
    protected void setUniformMatrix4f(final int location, final float[] matrix) {
        runOnDraw(new Runnable() {
            @Override
            public void run() {
                GLES20.glUniformMatrix4fv(location, 1, false, matrix, 0);
            }
        });
    }


    protected void runOnDraw(Runnable runnable) {
        synchronized (mRunOnDraw) {
            mRunOnDraw.addLast(runnable);
        }
    }

    protected void onDrawArraysPre() {

    }


    protected void glViewport(int x, int y, int width, int height) {
        GLES30.glViewport(x, y, width, height);
    }

    protected void glClearColor(float red, float green, float blue, float alpha) {
        GLES30.glClearColor(red, green, blue, alpha);
    }

    protected void glClear(int mask) {
        GLES30.glClear(mask);
    }

    protected static void glEnableVertexAttribArray(int index) {
        GLES30.glEnableVertexAttribArray(index);
    }

    protected void glDisableVertexAttribArray(int index) {
        GLES30.glDisableVertexAttribArray(index);
    }

    protected int glGetAttribLocation(String name) {
        return GLES30.glGetAttribLocation(mProgram, name);
    }

    protected int glGetUniformLocation(String name) {
        return GLES30.glGetUniformLocation(mProgram, name);
    }

    protected void glUniformMatrix4fv(int location, int count, boolean transpose, float[] value, int offset) {
        GLES30.glUniformMatrix4fv(location, count, transpose, value, offset);
    }

    protected void glDrawArrays(int mode, int first, int count) {
        GLES30.glDrawArrays(mode, first, count);
    }

    protected void glDrawElements(int mode, int count, int type, java.nio.Buffer indices) {
        GLES30.glDrawElements(mode, count, type, indices);
    }

    protected void orthoM(String name, int width, int height) {
        ProjectionMatrixUtil.orthoM(mProgram, width, height, name);
    }

    protected void glVertexAttribPointer(
            int indx,
            int size,
            int type,
            boolean normalized,
            int stride,
            java.nio.Buffer ptr) {
        GLES30.glVertexAttribPointer(indx, size, type, normalized, stride, ptr);
    }

    protected void glActiveTexture(int texture) {
        GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
    }

    protected void glBindTexture(int target, int texture) {
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, texture);
    }

    protected void glUniform1i(int location, int x) {
        GLES20.glUniform1i(location, x);
    }

    public int getProgram() {
        return mProgram;
    }
}

在Render中改变的地方就是onDrawFrame()方法中去调用Filter.onDraw()方法:

    @Override
    public void onDrawFrame(GL10 gl) {
        glClear(GLES30.GL_DEPTH_BUFFER_BIT | GLES30.GL_COLOR_BUFFER_BIT);
        adjustVideoSize();
        synchronized (this) {
            if (mUpdateSurfaceTexture) {
                mSurfaceTexture.updateTexImage();
                mUpdateSurfaceTexture = false;
            }
        }
        runAll(mRunOnDraw);
        mFilter.onDraw(mTextureId, mVertextBuffer, mTextureBuffer);
    }

常用概念整理

  • OpenGL: 一个定义了函数布局和输出的图形API的正式规范。
  • GLAD: 一个拓展加载库,用来为我们加载并设定所有OpenGL函数指针,从而让我们能够使用所有(现代)OpenGL函数。
  • 视口(Viewport): 我们需要渲染的窗口。
  • 图形管线(Graphics Pipeline): 一个顶点在呈现为像素之前经过的全部过程。
  • 着色器(Shader): 一个运行在显卡上的小型程序。很多阶段的图形管道都可以使用自定义的着色器来代替原有的功能。
  • 标准化设备坐标(Normalized Device Coordinates, NDC): 顶点在通过在剪裁坐标系中剪裁与透视除法后最终呈现在的坐标系。所有位置在NDC下-1.0到1.0的顶点将不会被丢弃并且可见。
  • 顶点缓冲对象(Vertex Buffer Object): 一个调用显存并存储所有顶点数据供显卡使用的缓冲对象。
  • 顶点数组对象(Vertex Array Object): 存储缓冲区和顶点属性状态。
  • 元素缓冲对象(Element Buffer Object,EBO),也叫索引缓冲对象(Index Buffer Object,IBO): 一个存储元素索引供索引化绘制使用的缓冲对象。
  • Uniform: 一个特殊类型的GLSL变量。它是全局的(在一个着色器程序中每一个着色器都能够访问uniform变量),并且只需要被设定一次。
  • 纹理(Texture): 一种包裹着物体的特殊类型图像,给物体精细的视觉效果。
  • 纹理缠绕(Texture Wrapping): 定义了一种当纹理顶点超出范围(0, 1)时指定OpenGL如何采样纹理的模式。
  • 纹理过滤(Texture Filtering): 定义了一种当有多种纹素选择时指定OpenGL如何采样纹理的模式。这通常在纹理被放大情况下发生。
  • 多级渐远纹理(Mipmaps): 被存储的材质的一些缩小版本,根据距观察者的距离会使用材质的合适大小。
  • 纹理单元(Texture Units): 通过绑定纹理到不同纹理单元从而允许多个纹理在同一对象上渲染。
  • 向量(Vector): 一个定义了在空间中方向和/或位置的数学实体。
  • 矩阵(Matrix): 一个矩形阵列的数学表达式。
  • GLM: 一个为OpenGL打造的数学库。
  • 局部空间(Local Space): 一个物体的初始空间。所有的坐标都是相对于物体的原点的。
  • 世界空间(World Space): 所有的坐标都相对于全局原点。
  • 观察空间(View Space): 所有的坐标都是从摄像机的视角观察的。
  • 裁剪空间(Clip Space): 所有的坐标都是从摄像机视角观察的,但是该空间应用了投影。这个空间应该是一个顶点坐标最终的空间,作为顶点着色器的输出。OpenGL负责处理剩下的事情(裁剪/透视除法)。
  • 屏幕空间(Screen Space): 所有的坐标都由屏幕视角来观察。坐标的范围是从0到屏幕的宽/高。
  • LookAt矩阵: 一种特殊类型的观察矩阵,它创建了一个坐标系,其中所有坐标都根据从一个位置正在观察目标的用户旋转或者平移。
  • 欧拉角(Euler Angles): 被定义为偏航角(Yaw),俯仰角(Pitch),和滚转角(Roll)从而允许我们通过这三个值构造任何3D方向。


results matching ""

    No results matching ""